from queue_status import queue_status
import threading
import re
import time
import logging
import functools
import queue_globals as vars
import simplejson as json
from queue.master_notifier import master_notifier, run_proc


class queue_handler:
    CONSUMER_BLOCKING = 0
    CONSUMER_NON_BLOCKING = 1
    msgACKre = re.compile("([0-9]+)$")

    def __init__(self, dbName):
        self.dbName = dbName
        self.dataStore = vars.DataStore.DataStore(dbName)
        if self.dataStore() is None:
            raise RuntimeError("The database '%s' does not exist or can not be opened" % (self.dbName))

        self.cv = threading.Condition()
        self.pendingRequests = [ ]
        self.queues = { }
        self.startTime = time.time()
        self.workerThread = threading.Thread(target=self).start()

    def set_state(self, queueName, queueStatus):
        if queueName not in self.queues:
            self.queues[queueName] = queue_status(queueStatus)
        else:
            self.queues[queueName].status = queueStatus

    def processAndDispatchMessageToConsumer(self, message, consumerRequest):
        consumerRequest['Content-Length'] = len(message['message'])
        consumerRequest['X-MessageID']    = message['messageID']
        consumerRequest.push(message['message'])
        consumerRequest.done_enqueue()

        if consumerRequest.pymq_autoACK:
            self.dataStore.ackMessage(consumerRequest.pymq_queue,
                                      message['messageID'])


    def process_single_request(self, request):
        dataStore      = self.dataStore
        requestType    = request.pymq_type
        requestQueue   = request.pymq_queue
        consumerMode   = queue_handler.CONSUMER_BLOCKING
        request.pymq_autoACK = False

        if requestType == "addQueue" and requestQueue not in self.queues:
            self.queues[requestQueue] = queue_status(request.pymq_queue_status)
        thisQueue = self.queues[requestQueue]

        ackHeader = request.get_header('X-ACKMessage')
        if ackHeader:
            # logging.debug("Got: X-ACKMessage")
            m = queue_handler.msgACKre.match(ackHeader)
            if m is not None:
                messageID = int(m.group(1))
                if messageID > 0:
                    if (thisQueue.status & queue_status.STATUS_REJECTING_CONSUMERS != 0):
                        request.error_enqueue(423)
                        return

                    # logging.debug("Message to ACK: %d" % messageID)
                    dataStore.ackMessage(requestQueue, messageID)
            else:
                # Send back a 400
                request.error_enqueue(400)
                return

        if request.get_header('X-NoBlock'):
            # We assume that the consumer wants immediate response
            # if this header is set
            queue_handler.CONSUMER_NON_BLOCKING

        if request.get_header('X-AutoACK'):
            request.pymq_autoACK = True


        if requestType == 'consumeMessage':
            if thisQueue.status & queue_status.STATUS_REJECTING_CONSUMERS != 0:
                request.error_enqueue(423)
                return

            # Enable this check later
            # if request.command != "get":
            #     request.error_enqueue(405)
            #     return

            # message = [{ "queueName": "test01", "message": "helloWorld", "messageID": 4 }]
            messages = dataStore.peekMessage(requestQueue)
            if messages and len(messages) > 0:
                message = messages[0]
                # Send this as the response
                self.processAndDispatchMessageToConsumer(message, request)
                thisQueue.msgOut += 1
                return
            else:
                # Don't return anything now unless the consumer is in non-blocking mode
                if consumerMode == queue_handler.CONSUMER_NON_BLOCKING:
                    request.error_enqueue(449)
                    return

                # Nothing to consume here and we are in blocking mode.
                # Add ourselves to the list of registered listeners
                thisQueue.consumers.append(request)
                return

        elif requestType == 'produceMessage':
            if thisQueue.status & queue_status.STATUS_REJECTING_PRODUCERS != 0:
                request.error_enqueue(423)
                return

            # Save the payload to the DB and dispatch the message to
            # waiting consumers (if any)
            insertSuccess = dataStore.enqueueMessage(requestQueue, request.pymq_payload)

            if not insertSuccess:
                request.error_enqueue(500)
                return

            thisQueue.msgIn += 1
            request.done_enqueue()
            getMessageFromDateStore = False

            while len(thisQueue.consumers) > 0:
                # Dispatch message to consumer
                # TODO: Fix thisQueue.consumers and the message below
                if getMessageFromDateStore == False:
                    lastInsertId = dataStore.lastInsertedMessageID()
                    messages = [ {
                                 "queueName": requestQueue,
                                 "message": request.pymq_payload,
                                 "messageID": lastInsertId,
                    } ]
                    getMessageFromDateStore = True
                else:
                    # Fetch a message from the data store
                    messages = dataStore.peekMessages(requestQueue, len(thisQueue.consumers))
                    # The check below checks for not NONEness of messages
                    # as well as len(messages) != 0
                    if not messages:
                        break

                while len(messages) > 0 and len(thisQueue.consumers) > 0:
                    # We pop the request BEFORE it is sent. This is to avoid
                    # situations where a failing response can block a queue.
                    # Correction: The send() is NOT synchronous, so it really
                    # doesn't matter in this case
                    message = messages[0]
                    consumerRequest = thisQueue.consumers.popleft()
                    self.processAndDispatchMessageToConsumer(message, consumerRequest)
                    thisQueue.msgOut += 1

                    if consumerRequest.pymq_autoACK:
                        messages = messages[1:]

                return

        elif requestType == 'addQueue':
            dataStore.provisionQueue(requestQueue)
            request.push(json.dumps({"queueName": request.pymq_queue,
                                     "status": request.pymq_queue_status,
                                     "ownerDB": request.pymq_db}))
            request.done_enqueue()
            vars.enqueuer.enqueue_request(run_proc(functools.partial(vars.master_notifier.queue_added,
                                                                     queueName=requestQueue)))

        elif requestType == 'dropQueue':
            # Remove all entries from the DB and from the local cache
            deleteSuccess = dataStore.unprovisionQueue(requestQueue)
            if not deleteSuccess:
                request.error_enqueue(500)
                return
            del self.queues[requestQueue]
            request.done_enqueue()
            vars.enqueuer.enqueue_request(run_proc(functools.partial(vars.master_notifier.queue_dropped,
                                                                     queueName=requestQueue)))

        elif requestType == "stats":
            request.push(json.dumps(thisQueue.getStatsDict(self.startTime)))
            request.done_enqueue()
            pass

        elif requestType == 'rejectAll':
            # Mark the queue status
            thisQueue.status = queue_status.STATUS_REJECTING_ALL
            for waitingConsumer in thisQueue.consumers:
                waitingConsumer.error_enqueue(423)
            thisQueue.consumers = []
            request.done_enqueue()

        elif requestType == 'rejectConsumers':
            # Mark the queue status
            thisQueue.status = queue_status.STATUS_REJECTING_CONSUMERS
            for waitingConsumer in thisQueue.consumers:
                waitingConsumer.error_enqueue(423)
            thisQueue.consumers = []
            request.done_enqueue()

        elif requestType == 'rejectProducers':
            # Mark the queue status
            thisQueue.status = queue_status.STATUS_REJECTING_PRODUCERS
            request.done_enqueue()

        elif requestType == 'resumeQueue':
            # Mark the queue status
            thisQueue.status = queue_status.STATUS_RUNNING
            request.done_enqueue()

        elif requestType == "loadQueue":
            dataStore.loadQueue(self, request, request.pymq_payload)

        elif requestType == "dumpQueue":
            dataStore.dumpQueue(self, request, request.pymq_payload)

        elif requestType == "loadQueueDone":
            # TODO: Adjust the stats for the queue that
            # is being loaded into (if need be)
            thisQueue.msgIn += request.pymq_num_affected_rows
            request.push(str(request.pymq_num_affected_rows))
            request.done_enqueue()

        elif requestType == "dumpQueueDone":
            request.push(str(request.pymq_num_affected_rows))
            request.done_enqueue()


    def __call__(self):
        while (True):
            self.cv.acquire()
            while len(self.pendingRequests) == 0:
                self.cv.wait()
            requestsToProcess = self.pendingRequests
            self.pendingRequests = []
            self.cv.release()

            # Process the requests in requestsToProcess
            for request in requestsToProcess:
                try:
                    self.process_single_request(request)
                except Exception, e:
                    logging.exception("Caught exception in __call__: " + str(e))


    def handleRequest(self, request):
        self.cv.acquire()
        self.pendingRequests.append(request)
        self.cv.notify()
        self.cv.release()
